﻿#region

using System;
using System.Collections.Concurrent;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;

#endregion

namespace SharpObservation.Tests.Dummy
{
    public class MockSynchronizationContext : SynchronizationContext, IDisposable
    {
        private class SendOrPost
        {
            private ManualResetEvent _waitHandle;
            public SendOrPostCallback Callback { get; set; }
            public object Data { get; set; }

            public WaitHandle WaitHandle
            {
                get { return _waitHandle ?? (_waitHandle = new ManualResetEvent(false)); }
            }

            public void Execute()
            {
                try
                {
                    Callback(Data);
                }
                finally
                {
                    if (_waitHandle != null)
                        _waitHandle.Set();
                }
            }
        }


        private readonly ConcurrentQueue<SendOrPost> _actionQueue = new ConcurrentQueue<SendOrPost>();
        private readonly SynchronizationContext _previousContext;
        private readonly AutoResetEvent _queueSignal = new AutoResetEvent(false);

        private TimeSpan _timeout = new TimeSpan(500);
        public TimeSpan Timeout
        {                         
            get { return _timeout; }
            set { _timeout = value; }
        }

        public MockSynchronizationContext()
        {
            _previousContext = Current;
            SetSynchronizationContext(this);
        }

        public void Dispose()
        {
            //Assert.IsTrue(_actionQueue.IsEmpty);
            SetSynchronizationContext(_previousContext);
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            _actionQueue.Enqueue(new SendOrPost { Callback = d, Data = state });
            _queueSignal.Set();
        }

        public override void Send(SendOrPostCallback d, object state)
        {
            var info = new SendOrPost { Callback = d, Data = state };
            var handle = info.WaitHandle;
            _actionQueue.Enqueue(info);
            _queueSignal.Set();
            handle.WaitOne();
        }


        public void Run(CancellationTokenSource tokenSource)
        {
            var token = tokenSource.Token;
            SendOrPost item;

            while (1 == WaitHandle.WaitAny(new[] { token.WaitHandle, _queueSignal }, Timeout))
            {
                if (_actionQueue.TryDequeue(out item))
                    item.Execute();
            }
        }
    }
}